// 可以链式调用

// 普通对象只能用字符串当作键,当你将一个 DOM 节点作为对象data的键，但是由于对象只接受字符串作为键名，所以element被自动转为字符串[object HTMLDivElement]
// Object 结构提供了“字符串—值”的对应，Map 结构提供了“值—值”的对应，是一种更完善的 Hash 结构实现。
// 初始化Map需要一个二维数组; 由于一个key只能对应一个value，所以，多次对一个key放入value，后面的值会把前面的值冲掉

// Map 结构原生提供三个遍历器生成函数和一个遍历方法。

// keys()：返回键名的遍历器。- key
// values()：返回键值的遍历器。- value
// entries()：返回所有成员的遍历器。//数组[key, value]  - map[Symbol.iterator] === map.entries
// forEach()：遍历 Map 的所有成员。
var m = new Map();
var m = new Map([
  ['Michael', 95]
]);
m.get('Michael'); // 95

m.set('Adam', 67); // 添加新的key-value
m.set('Bob', 59);
m.has('Adam'); // 是否存在key 'Adam': true
m.get('Adam'); // 67
m.delete('Adam'); // 删除key 'Adam'
m.get('Adam'); // undefined
console.log(m, m.size)
m.forEach((value, key) => {
  // console.log(value, key)
  console.log("Key: %s, Value: %s", key, value);
});


// forEach方法还可以接受第二个参数，用来绑定this。
const reporter = {
  report: function(key, value) {
    console.log("Key: %s, Value: %s", key, value);
  }
};
m.forEach(function(value, key, map) {
  this.report(key, value);
}, reporter);

// 等同于使用map.entries()
for (let [key, value] of m) {
  console.log(key, value);
}

// 不仅仅是数组，任何具有 Iterator 接口、且每个成员都是一个双元素的数组的数据结构（详见《Iterator》一章）都可以当作Map构造函数的参数。这就是说，Set和Map都可以用来生成新的 Map
const set = new Set([
  ['foo', 1],
  ['bar', 2]
]);
const m1 = new Map(set);

// 只有对同一个对象的引用，Map 结构才将其视为同一个键。这一点要非常小心。
// 如果 Map 的键是一个简单类型的值（数字、字符串、布尔值），则只要两个值严格相等，Map 将其视为一个键
const map = new Map();
map.set(['a'], 555);
map.get(['a']) // undefined (不是同一对象的引用)


// map转对象: 如果所有 Map 的键都是字符串，它可以无损地转为对象。如果有非字符串的键名，那么这个键名会被转成字符串，再作为对象的键名。
function strMapToObj(strMap) {
  let obj = Object.create(null);
  for (let [k,v] of strMap) {
    obj[k] = v;
  }
  return obj;
}

// map可转成对象或者数组再分别转成json


// WeakMap只接受对象作为键名（null除外），不接受其他类型的值作为键名。
// WeakMap的键名所指向的对象，不计入垃圾回收机制。
// WeakMap的设计目的在于，有时我们想在某个对象上面存放一些数据，但是这会形成对于这个对象的引用。请看下面的例子。
// const e1 = document.getElementById('foo');
// const e2 = document.getElementById('bar');
// const arr = [
//   [e1, 'foo 元素'],
//   [e2, 'bar 元素'],
// ];



console.log("--你的浏览器支持Map和Set！--")


// 重复元素在Set中自动被过滤
// et 内部判断两个值是否不同，使用的算法叫做“Same-value-zero equality”，它类似于精确相等运算符（===），主要的区别是在Set内部NaN等于自身，而精确相等运算符认为NaN不等于自身。
// 两个对象总是不相等的。
// Set的遍历顺序就是其插入顺序

/*
Set 结构的实例有以下属性。

Set.prototype.constructor：构造函数，默认就是Set函数。
Set.prototype.size：返回Set实例的成员总数。
Set 实例的方法分为两大类：操作方法（用于操作数据）和遍历方法（用于遍历成员）。下面先介绍四个操作方法。

add(value)：添加某个值，返回 Set 结构本身。
delete(value)：删除某个值，返回一个布尔值，表示删除是否成功。
has(value)：返回一个布尔值，表示该值是否为Set的成员。
clear()：清除所有成员，没有返回值。

// 由于 Set 结构没有键名，只有键值（或者说键名和键值是同一个值），所以keys方法和values方法的行为完全一致。
Set.prototype[Symbol.iterator] === Set.prototype.values;表明可直接通过for...of遍历Set
keys()：返回键名的遍历器 // red
values()：返回键值的遍历器 // red
// entries方法返回的遍历器，同时包括键名和键值，所以每次输出一个数组，它的两个成员完全相等。
entries()：返回键值对的遍历器 // ["red", "red"]
forEach()：使用回调函数遍历每个成员(与数组一样,参数为键值、键名、集合本身)
间接使用map和filter:实现并集（Union）、交集（Intersect）和差集（Difference）
  set = new Set([...set].filter(x => (x % 2) == 0));
  set = new Set([...set].map(x => x * 2));
*/
var s1 = new Set(); // 空Set
var s2 = new Set([1, 2, 3]); // 含1, 2, 3
s2.add(4);
s2.add(NaN);
s2.add(NaN);
s2.add({});
s2.add({});
s2.delete(3);
for (let i of s2) {
  console.log(i);
}
// 扩展运算符（...）内部使用for...of循环
console.log([...s2], Array.from(s2))
console.log(s1, s2, s2.size)

/*
WeakSet 的成员只能是对象，而不能是其他类型的值。

WeakSet 中的对象都是弱引用，即垃圾回收机制不考虑 WeakSet 对该对象的引用;不计入垃圾回收机制(没有引用计数);随着对象在外部消失，它在 WeakSet 里面的引用就会自动消失。因此ES6 规定 WeakSet 不可遍历

WeakSet 可以接受一个数组或类似数组的对象作为参数。（实际上，任何具有 Iterable 接口的对象，都可以作为 WeakSet 的参数。）

WeakSet 结构有以下三个方法。

WeakSet.prototype.add(value)：向 WeakSet 实例添加一个新成员。
WeakSet.prototype.delete(value)：清除 WeakSet 实例的指定成员。
WeakSet.prototype.has(value)：返回一个布尔值，表示某个值是否在 WeakSet 实例之中。

WeakSet 没有size属性，没有办法遍历它的成员。

WeakSet 的一个用处，是储存 DOM 节点，而不用担心这些节点从文档移除时，会引发内存泄漏。
另一个删除实例的时候，不用考虑foos，也不会出现内存泄漏。
const foos = new WeakSet()
class Foo {
  constructor() {
    foos.add(this)
  }
  method () {
    if (!foos.has(this)) {
      throw new TypeError('Foo.prototype.method 只能在Foo的实例上调用！');
    }
  }
}
*/

// 代码中，a是一个数组，它有两个成员，也都是数组。将a作为 WeakSet 构造函数的参数，a的成员会自动成为 WeakSet 的成员。
// 注意，是a数组的成员成为 WeakSet 的成员，而不是a数组本身。这意味着，数组的成员只能是对象。
const a = [
  [1, 2],
  [3, 4]
];
const ws1 = new WeakSet(a); // WeakSet {[1, 2], [3, 4]}

const ws = new WeakSet();
const obj = {};
const foo = {};
// ws.add(window ? window : global);
ws.add(obj);
// ws.has(window ? window : global); // true
ws.has(foo); // false
// ws.delete(window ? window : global);
// ws.has(window ? window : global); // false